﻿using System;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;
using Gallio.Framework;
using MbUnit.Framework;
using Moq;
using MvcContrib;
using MvcContrib.PortableAreas;
using MvcContrib.TestHelper;
using OpenIdPortableArea.Areas.OpenId;
using OpenIdPortableArea.Areas.OpenId.Controllers;
using OpenIdPortableArea.Areas.OpenId.Models;
using OpenIdPortableArea.Areas.OpenId.Services;
using OpenIdPortableArea.Messages;
using Should;

namespace OpenIdPortableAreaTests.Controllers
{
    [TestFixture]
    public class OpenIdControllerTests
    {
        private Mock<IApplicationBus> _bus;
        private OpenIdController _controller;
        private IAuthenticationService _service;

        [SetUp]
        public void SetUp()
        {
            _bus = new Mock<IApplicationBus>();
            Bus.Instance = null;
            _service = new FakeAuthenticationService();
            _controller = new OpenIdController(_service, _bus.Object);

            var routes = new RouteCollection();
            var area = new OpenIdAreaRegistration();
            area.RegisterRoutes(new AreaRegistrationContext("OpenId", routes));

            var context = FakeHttpContext();
            _controller.HttpRequest = context.Request;

            _controller.Url = new UrlHelper(new RequestContext(context, new RouteData()), routes);
        }

        public static HttpContextBase FakeHttpContext()
        {
            var context = new Mock<HttpContextBase>();
            var request = new Mock<HttpRequestBase>();
            var response = new Mock<HttpResponseBase>();
            var session = new Mock<HttpSessionStateBase>();
            var server = new Mock<HttpServerUtilityBase>();
            var user = new Mock<IPrincipal>();
            var identity = new Mock<IIdentity>();

            request.Setup(req => req.ApplicationPath).Returns("/");
            request.Setup(req => req.AppRelativeCurrentExecutionFilePath).Returns("~/");
            request.Setup(req => req.PathInfo).Returns(string.Empty);
            response.Setup(res => res.ApplyAppPathModifier(It.IsAny<string>()))
                .Returns((string virtualPath) => virtualPath);
            request.SetupGet(x => x.Url).Returns(new Uri("http://localhost:1234/"));
            request.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection());

            user.Setup(usr => usr.Identity).Returns(identity.Object);
            identity.SetupGet(ident => ident.IsAuthenticated).Returns(true);

            context.Setup(ctx => ctx.Request).Returns(request.Object);
            context.Setup(ctx => ctx.Response).Returns(response.Object);
            context.Setup(ctx => ctx.Session).Returns(session.Object);
            context.Setup(ctx => ctx.Server).Returns(server.Object);
            context.Setup(ctx => ctx.User).Returns(user.Object);

            return context.Object;
        }

        [TearDown]
        public void TearDown()
        {
            _bus = null;
            _service = null;
            _controller = null;
        }

        [Test]
        public void Login_action_should_return_Login_view()
        {
            var result = _controller.Login("");
            result.AssertViewRendered().ViewName.ShouldEqual("Login");
        }

        [Test]
        public void Login_action_should_return_Login_view_for_invalid_url()
        {
            var input = new LoginInput { OpenIdUrl = "asdf" };

            var result = _controller.Login(input);

            result.AssertViewRendered().ViewName.ShouldEqual("Login");
        }

        [Test]
        public void Login_action_should_send_ClaimsRequestMessage()
        {
            ((FakeAuthenticationService)_service).Valid = true;

            _controller.Login(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com/" });

            _controller.LastSentMessage.ShouldBeType<ClaimsRequestMessage>();
        }

        [Test]
        public void Login_action_should_return_RedirectResult_for_valid_url()
        {
            var fake = (FakeAuthenticationService)_service;
            fake.Valid = true;
            fake.RedirectResult = new RedirectResult("http://johncoder.myopenid.com");

            var result = _controller.Login(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com/" });

            result.AssertHttpRedirect().Url.ShouldEqual("http://johncoder.myopenid.com");
        }

        [Test]
        public void Login_action_should_return_Login_view_for_error()
        {
            var fake = (FakeAuthenticationService)_service;
            fake.Valid = true;
            fake.AddClaimAction = () => { throw new InvalidOperationException(); };

            var result = _controller.Login(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com/" });

            result.AssertViewRendered().ViewName.ShouldEqual("Login");
        }

        [Test]
        public void Login_action_should_generate_correct_callbackUrl()
        {
            Uri url = _controller.GetCallBackUrl("/Home/Account");
            url.ToString().ShouldEqual("http://localhost:1234/OpenId/Result?returnUrl=/Home/Account");
        }

        [Test]
        public void Logout_action_should_send_LoggingOutMessage()
        {
            Bus.AddMessageHandler(typeof (FakeLoggingOutMessageHandler));
            _controller = new OpenIdController(_service, Bus.Instance);
            var result = _controller.Logout("~/");
            _controller.LastSentMessage.ShouldBeType<LoggingOutMessage>();
            result.AssertViewRendered().ViewName.ShouldEqual("Logout");
        }

        [Test]
        public void Logout_action_should_send_LoggedOutMessage()
        {
            _controller.Logout("~/");
            _controller.LastSentMessage.ShouldBeType<LoggedOutMessage>();
        }

        [Test]
        public void Logout_action_should_return_Login_url()
        {
            var result = _controller.Logout();

            result.AssertHttpRedirect().Url.ShouldEqual("~/OpenId/Login");
        }

        [Test]
        public void Logout_action_should_return_Logout_view()
        {
            _controller = new OpenIdController(_service, Bus.Instance);
            Bus.AddMessageHandler(typeof(FakeLoggingOutMessageHandler));

            var result = _controller.Logout("~/");

            result.AssertViewRendered().ViewName.ShouldEqual("Logout");
        }

        [Test]
        public void Result_action_should_redirect_to_Success_by_default()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Authenticated;

            var result = _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com" });

            result.AssertHttpRedirect().Url.ShouldEqual("~/OpenId/Success");
        }

        [Test]
        public void Result_action_should_send_AuthenticatedMessage_for_Authenticated_status()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Authenticated;

            _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com" });

            _controller.LastSentMessage.ShouldBeType<AuthenticatedMessage>();
        }

        [Test]
        public void Result_action_should_send_UnauthenticatedMessage_for_Canceled_status()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Canceled;

            _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com" });

            _controller.LastSentMessage.ShouldBeType<UnauthenticatedMessage>();
            ((UnauthenticatedMessage)_controller.LastSentMessage).Message.ShouldEqual("OpenID not Authenticated: Canceled");
        }

        [Test]
        public void Result_action_should_send_UnauthenticatedMessage_for_Failed_status()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Failed;

            _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com" });

            _controller.LastSentMessage.ShouldBeType<UnauthenticatedMessage>();
            ((UnauthenticatedMessage)_controller.LastSentMessage).Message.ShouldEqual("OpenID not Authenticated: Failed");
        }

        [Test]
        public void Result_action_should_return_Login_view_for_Unauthenticated()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Canceled;

            var result = _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com" });

            result.AssertViewRendered().ViewName.ShouldEqual("Login");
        }

        [Test]
        public void Result_action_should_return_RedirectResult_to_ReturnUrl()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Authenticated;

            var result = _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com", ReturnUrl = "/Home/Account" });

            _controller.LastSentMessage.ShouldBeType<AuthenticatedMessage>();
            result.AssertResultIs<RedirectResult>();
            (result as RedirectResult).Url.ShouldEqual("/Home/Account");
        }

        [Test]
        public void Result_action_should_return_custom_ActionResult_EmptyResult()
        {
            var service = (FakeAuthenticationService)_service;
            service.Status = AuthenticationStatus.Authenticated;
            Bus.AddMessageHandler(typeof(FakeAuthenticatedMessageHandler));
            _controller = new OpenIdController(service, Bus.Instance);

            var result = _controller.Result(new LoginInput { OpenIdUrl = "http://johncoder.myopenid.com" });

            _controller.LastSentMessage.ShouldBeType<AuthenticatedMessage>();
            result.AssertResultIs<EmptyResult>();
        }

        [Test]
        public void GetCallBackUrl_should_return_Uri_with_root_url()
        {
            _controller.HttpRequest.Headers.Add("HOST", "http://localhost/");
            var result = _controller.GetCallBackUrl("/Home/Account");
            var expected = "http://localhost/OpenId/Result?returnUrl=/Home/Account";
            Assert.AreEqual(expected, result.ToString());
        }

        [Test]
        public void GetCallBackUrl_should_return_Uri_with_root_url_and_port_of_the_request()
        {
            _controller.HttpRequest.Headers.Add("HOST", "localhost:54321");
            var result = _controller.GetCallBackUrl("/Home/Account");
            var expected = "http://localhost:54321/OpenId/Result?returnUrl=/Home/Account";
            Assert.AreEqual(expected, result.ToString());
        }
    }

    public class FakeLoggingOutMessageHandler : MessageHandler<LoggingOutMessage>
    {
        public bool MessageHandled { get; set; }

        public override void Handle(LoggingOutMessage message)
        {
            MessageHandled = true;
            TestLog.WriteLine("Handling LoggingOutMessage");
            message.AutoLogout = false;
        }
    }

    public class FakeLoggedOutMessageHandler : MessageHandler<LoggedOutMessage>
    {
        public bool MessageHandled { get; set; }

        public override void Handle(LoggedOutMessage message)
        {
            MessageHandled = true;
            TestLog.WriteLine("Handling LoggedOutMessage");
        }
    }

    public class FakeAuthenticatedMessageHandler : MessageHandler<AuthenticatedMessage>
    {
        public bool MessageHandled { get; set; }

        public override void Handle(AuthenticatedMessage message)
        {
            MessageHandled = true;
            TestLog.WriteLine("Handling AuthenticatedMessage");
            message.Result = new EmptyResult();
        }
    }

    public class FakeAuthenticationService : IAuthenticationService
    {
        public bool Valid { get; set; }
        public ActionResult RedirectResult { get; set; }

        public Action AddClaimAction { get; set; }
        public AuthenticationStatus Status { get; set; }

        public void AddClaim(ClaimsRequest claim)
        {
            if (AddClaimAction != null)
                AddClaimAction();
        }

        public AuthenticationStatus GetAuthenticationStatus()
        {
            return Status;
        }

        public bool IsValid(string openIdUrl, Uri callbackUrl)
        {
            return Valid;
        }

        public void Logout()
        {

        }

        public ActionResult Redirect()
        {
            return RedirectResult;
        }

        public void SetMessage(AuthenticatedMessage message)
        {

        }
    }
}